/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package br.com.sapereaude.maskedEditText;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.agp.components.TextField;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 自定义输入框
 *
 * @since 2021-04-12
 */
public class MaskedEditText extends TextField implements Text.TextObserver {
    private static final String SPACE = " ";
    private static final String REGEX =
        "[`a-zA-Z~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
    private static final int DELAY_TIME = 2500;
    private static final int TIME = 1500;
    private static final int CONSTANT_2 = 2;
    private static final int CONSTANT_3 = 3;
    private static final int CONSTANT = -1;
    private static final String CONSTANT_STR = "*";
    private String mask;
    private char charRepresentation;
    private int[] rawToMask;
    private RawText rawText;
    private boolean isIgnore;
    private boolean isKeepHint;
    private boolean isFirstCheck;
    private boolean isInitialized;
    private boolean isEditingAfter;
    private boolean isEditingBefore;
    private boolean isShouldKeepText;
    private boolean isEnableImeAction;
    private boolean isEditingOnChanged;
    private boolean isSelectionChanged;
    private int cursorIndex;
    private int[] maskToRaw; // 长度必须要与hint 的长度一样否则会报错
    private int maxRawLength;
    private int lastValidMaskPosition;
    private FocusChangedListener focusChangeListener;
    private String allowedChars;
    private String deniedChars;
    private int recordTextLength = 0;
    private int bubbleHeight;
    private int bubbleWidth;
    private int index = 0; // 记录索引循环位置
    private int recordIndex = 0; // 记录当前缺少字符的位置 默认初始化3
    private String recordText = ""; // 记录旧文本
    private EventHandler eventHandler;
    private long recordCursorTime;

    private final EditorActionListener onEditorActionListener = new EditorActionListener() {
        @Override
        public boolean onTextEditorAction(int actionId) {
            switch (actionId) {
                default:
                    return true;
            }
        }
    };
    private final InnerEvent innerEvent = InnerEvent.get(new Runnable() {
        @Override
        public void run() {
            // 判断当前秒数与记录秒数是否大于2.5秒
            if (System.currentTimeMillis() - recordCursorTime >= DELAY_TIME) {
                if (getBubbleWidth() > 0 && getBubbleHeight() > 0) {
                    // 设置光标下方圆球为0
                    setBubbleSize(0, 0);
                }
            }
            eventHandler.sendEvent(InnerEvent.get(this), TIME); // 1.5秒一次循环发送消息
        }
    });

    /**
     * 构造方法
     *
     * @param context 上下文
     */
    public MaskedEditText(Context context) {
        this(context, null);
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrSet 属性集
     */
    public MaskedEditText(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrSet 属性集
     * @param styleName 类型名字
     */
    public MaskedEditText(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    /**
     * 初始化赋值
     *
     * @param attrSet 属性集
     */
    private void init(AttrSet attrSet) {
        eventHandler = new EventHandler(EventRunner.getMainEventRunner());
        eventHandler.sendEvent(innerEvent); // 发送消息
        bubbleHeight = getBubbleHeight(); // 光标下方的圆球高度
        bubbleWidth = getBubbleWidth(); // 光标下方的圆球宽度
        String mRepresentation = null;
        if (attrSet != null) {
            if (attrSet.getAttr("mask").isPresent()) {
                this.mask = attrSet.getAttr("mask").get().getStringValue();
            }
            if (attrSet.getAttr("allowed_chars").isPresent()) {
                this.allowedChars = attrSet.getAttr("allowed_chars").get().getStringValue();
            }
            if (attrSet.getAttr("enable_ime_action").isPresent()) {
                this.isEnableImeAction = attrSet.getAttr("enable_ime_action").get().getBoolValue();
            }
            if (attrSet.getAttr("denied_chars").isPresent()) {
                this.deniedChars = attrSet.getAttr("denied_chars").get().getStringValue();
            }
            if (attrSet.getAttr("char_representation").isPresent()) {
                mRepresentation = attrSet.getAttr("char_representation").get().getStringValue();
            }
            if (attrSet.getAttr("keep_hint").isPresent()) {
                this.isKeepHint = attrSet.getAttr("keep_hint").get().getBoolValue();
            }
        }
        isFirstCheck = true;

        if (mRepresentation == null) { // 这个判断很重要，直接判断不好直接回闪退
            charRepresentation = '#';
        } else {
            charRepresentation = mRepresentation.charAt(0);
        }
        cleanUp();
        initListener();
        if (!isEnableImeAction) {
            setEditorActionListener(onEditorActionListener);
        } else {
            setEditorActionListener(null);
        }
    }

    // 接口初始化
    private void initListener() {
        addTextObserver(this);
        setCursorChangedListener(new CursorChangedListener() {
            @Override
            public void onCursorChange(TextField textField, int selStart, int selEnd) {
                recordCursorTime = System.currentTimeMillis(); // 获取当前时间
                setBubbleSize(bubbleWidth, bubbleHeight); // 重置光标下方圆球大小
                cursorIndex = selStart;
                if (isInitialized && !isSelectionChanged) {
                    isSelectionChanged = true;
                }
            }
        });
    }

    private int erasingStart(int start) {
        int tempStart = start;
        while (tempStart > 0 && maskToRaw[tempStart] == CONSTANT) {
            tempStart--;
        }
        return tempStart;
    }

    private void cleanUp() {
        isInitialized = false;
        if (mask == null || mask.isEmpty()) {
            return;
        }
        generatePositionArrays();
        if (!isShouldKeepText || rawText == null) {
            rawText = new RawText();
        }
        isEditingBefore = true;
        isEditingOnChanged = true;
        isEditingAfter = true;
        if (hasHint() && rawText.length() == 0) {
            showMaskedTextHint();
        } else {
            showMaskedText();
        }
        isEditingBefore = false;
        isEditingOnChanged = false;
        isEditingAfter = false;

        maxRawLength = maskToRaw[previousValidPosition(mask.length() - 1)] + 1;
        lastValidMaskPosition = findLastValidMaskPosition();
        isInitialized = true;
        super.setFocusChangedListener(new FocusChangedListener() {
            @Override
            public void onFocusChange(Component component, boolean isHasFocus) {
                if (focusChangeListener != null) {
                    focusChangeListener.onFocusChange(component, isHasFocus);
                }
                if (hasFocus()) {
                    isSelectionChanged = false;
                    MaskedEditText.this.setTouchFocusable(true);
                }
            }
        });
    }

    /**
     * 设置焦点监听
     *
     * @param focusChangeListener 焦点回调
     */
    public void setFocusChangeListener(FocusChangedListener focusChangeListener) {
        this.focusChangeListener = focusChangeListener;
    }

    /**
     * 是否保持显示
     *
     * @param isShouldKeep 是否保持
     */
    public void setShouldKeepText(boolean isShouldKeep) {
        this.isShouldKeepText = isShouldKeep;
    }

    /**
     * 保持显示文本
     *
     * @return boolean是否保持显示
     */
    public boolean isKeepingText() {
        return isShouldKeepText;
    }

    /**
     * 保持隐藏
     *
     * @return boolean是否保持隐藏
     */
    public boolean isKeepHint() {
        return isKeepHint;
    }

    /**
     * 是否保持隐藏
     *
     * @param isKeep 是否保持隐藏
     */
    public void setKeepHint(boolean isKeep) {
        this.isKeepHint = isKeep;
        setText(getRawText());
    }

    /**
     * 设置输入格式
     *
     * @param mask 掩码
     */
    public void setMask(String mask) {
        this.mask = mask;
        cleanUp();
    }

    /**
     * 获取掩码
     *
     * @return String字符串
     */
    public String getMask() {
        return this.mask;
    }

    /**
     * 获取原始文本
     *
     * @return String字符串
     */
    public String getRawText() {
        return this.rawText.getText();
    }

    /**
     * 设置字符表示
     *
     * @param isChar 是否为字符
     */
    public void setCharRepresentation(char isChar) {
        this.charRepresentation = isChar;
        cleanUp();
    }

    /**
     * 获取展示字符
     *
     * @return char字符
     */
    public char getCharRepresentation() {
        return this.charRepresentation;
    }

    /**
     * 生成位置数组
     * Generates positions for values characters. For instance:
     * Input data: mask = "+7(###)###-##-##
     * After method execution:
     * rawToMask = [3, 4, 5, 6, 8, 9, 11, 12, 14, 15]
     * maskToRaw = [-1, -1, -1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, -1, 8, 9]
     * charsInMask = "+7()- " (and space, yes)
     */
    private void generatePositionArrays() {
        int[] aux = new int[mask.length()];
        maskToRaw = new int[mask.length()];
        String charsInMaskAux = "";

        int charIndex = 0;
        for (int ii = 0; ii < mask.length(); ii++) {
            char currentChar = mask.charAt(ii);
            if (currentChar == charRepresentation) {
                aux[charIndex] = ii;
                maskToRaw[ii] = charIndex++;
            } else {
                String charAsString = Character.toString(currentChar);
                if (!charsInMaskAux.contains(charAsString)) {
                    charsInMaskAux = charsInMaskAux.concat(charAsString);
                }
                maskToRaw[ii] = CONSTANT;
            }
        }
        rawToMask = new int[charIndex];
        System.arraycopy(aux, 0, rawToMask, 0, charIndex);
    }

    private boolean hasHint() {
        return getHint() != null;
    }

    private String makeMaskedTextWithHint() {
        StringBuilder ssb = new StringBuilder();
        int mtrv;
        for (int ii = 0; ii < mask.length(); ii++) {
            mtrv = maskToRaw[ii];
            if (mtrv != CONSTANT) {
                if (mtrv < rawText.length()) {
                    ssb.append(rawText.charAt(mtrv));
                } else {
                    boolean empty = getHint().isEmpty();
                    ssb.append(empty ? "9081234567".charAt(maskToRaw[ii]) : getHint().charAt(maskToRaw[ii]));
                }
            } else {
                ssb.append(mask.charAt(ii));
            }
        }
        return ssb.toString();
    }

    private String makeMaskedText() {
        int maskedTextLength;
        if (rawText.length() < rawToMask.length) {
            maskedTextLength = rawToMask[rawText.length()];
        } else {
            maskedTextLength = mask.length();
        }
        char[] maskedText = new char[maskedTextLength];
        for (int ii = 0; ii < maskedText.length; ii++) {
            int rawIndex = maskToRaw[ii];
            if (rawIndex == CONSTANT) {
                maskedText[ii] = mask.charAt(ii);
            } else {
                maskedText[ii] = rawText.charAt(rawIndex);
            }
        }
        return new String(maskedText);
    }

    private int previousValidPosition(int currentPosition) {
        int tempCurrentPosition = currentPosition;
        while (tempCurrentPosition >= 0 && maskToRaw[tempCurrentPosition] == CONSTANT) {
            tempCurrentPosition--;
            if (tempCurrentPosition < 0) {
                return nextValidPosition(0);
            }
        }
        return tempCurrentPosition;
    }

    private int nextValidPosition(int currentPosition) {
        int tempCurrentPosition = currentPosition;
        while (tempCurrentPosition < lastValidMaskPosition && maskToRaw[tempCurrentPosition] == CONSTANT) {
            tempCurrentPosition++;
        }
        if (tempCurrentPosition > lastValidMaskPosition) {
            return lastValidMaskPosition + 1;
        }
        return tempCurrentPosition;
    }

    private int findLastValidMaskPosition() {
        for (int ii = maskToRaw.length - 1; ii >= 0; ii--) {
            if (maskToRaw[ii] != CONSTANT) {
                return ii;
            }
        }
        return maskToRaw.length;
    }

    private int lastValidPosition() {
        if (rawText.length() == maxRawLength) {
            return rawToMask[rawText.length() - 1] + 1;
        }
        return nextValidPosition(rawToMask[rawText.length()]);
    }

    private Range calculateRange(int start, int end) {
        Range range = new Range();
        for (int ii = start; ii <= end && ii < mask.length(); ii++) {
            if (maskToRaw[ii] != CONSTANT) {
                if (range.getStart() == CONSTANT) {
                    range.setStart(maskToRaw[ii]);
                }
                range.setEnd(maskToRaw[ii]);
            }
        }

        if (end == mask.length()) {
            range.setEnd(rawText.length());
        }
        if (range.getStart() == range.getEnd() && start < end) {
            int newStart = maskToRaw[previousValidPosition(start)] - 1;
            if (newStart < range.getStart()) {
                range.setStart(newStart);
            }
        }
        return range;
    }

    private Range calculateRange2(int start, int end) {
        Range range = new Range();
        for (int ii = start; ii <= end && ii < mask.length(); ii++) {
            if (maskToRaw[ii] != CONSTANT) { // 判断是否删除了横杠-
                if (range.getStart() == CONSTANT) {
                    range.setStart(maskToRaw[ii]);
                }
                range.setEnd(maskToRaw[ii]);
            }
        }

        if (end == mask.length()) {
            range.setEnd(rawText.length());
        }
        if (range.getStart() == range.getEnd() && start < end) {
            int newStart = maskToRaw[previousValidPosition(start)];
            if (newStart < range.getStart()) {
                range.setStart(newStart);
            } else {
                range.setEnd(newStart + 1);
            }
        }
        return range;
    }

    private String clear(String string) {
        String tempString = string;
        if (deniedChars != null) {
            for (char cha : deniedChars.toCharArray()) {
                tempString = tempString.replace(Character.toString(cha), "");
            }
        }

        if (allowedChars != null) {
            StringBuilder builder = new StringBuilder(string.length());

            for (char ch : tempString.toCharArray()) {
                if (allowedChars.contains(String.valueOf(ch))) {
                    builder.append(ch);
                }
            }

            tempString = builder.toString();
        }

        return tempString;
    }

    private int fixSelection(int sel) {
        if (sel > lastValidPosition()) {
            return lastValidPosition();
        } else {
            return nextValidPosition(sel);
        }
    }

    /**
     * 找出两个字符串不相同的字符在哪个位置
     *
     * @param string 最新文本
     * @param oldString 旧文本
     */
    private void checkCharIndex(String string, String oldString) {
        if (string.length() > oldString.length()) { // 添加后的新文本大于老文本
            appendText(string, oldString);
        } else { // 删除后新文本小于老文本
            deleteText(string, oldString);
        }
        index = 0; // 恢复初始化
    }

    /**
     * 删除文本
     *
     * @param string 最新文本
     * @param oldString 旧文本
     */
    private void deleteText(String string, String oldString) {
        aa:
        for (int ii = 0; ii < oldString.length(); ii++) {
            if (index == string.length()) { // 说明已经到最后一位了
                recordIndex = ii;
                break aa;
            }
            bb:
            for (int jj = index; jj < string.length(); jj++) {
                if (oldString.charAt(ii) == string.charAt(jj)) {
                    index = jj + 1; // 继续寻找下一位
                    break bb;
                } else {
                    recordIndex = ii;
                    break aa;
                }
            }
        }
    }

    /**
     * 添加文本
     *
     * @param string 最新文本
     * @param oldString 旧文本
     */
    private void appendText(String string, String oldString) {
        aa:
        for (int ii = 0; ii < string.length(); ii++) {
            if (index == oldString.length()) { // 说明已经到最后一位了
                recordIndex = ii;
                break aa;
            }
            bb:
            for (int jj = index; jj < oldString.length(); jj++) {
                if (string.charAt(ii) == oldString.charAt(jj)) {
                    index = jj + 1; // 继续寻找下一位
                    break bb;
                } else {
                    recordIndex = ii;
                    break aa;
                }
            }
        }
    }

    // 显示输入文本内容
    private void showMaskedText() {
        String maskedText = makeMaskedText();
        setText(maskedText);
        recordText = maskedText; // 重新赋值
        recordTextLength = maskedText.length();
    }

    // 显示隐藏hint文本内容
    private void showMaskedTextHint() {
        String makeMaskedTextWithHint = makeMaskedTextWithHint();
        setText(makeMaskedTextWithHint);
        recordText = makeMaskedTextWithHint; // 重新赋值
        recordTextLength = makeMaskedTextWithHint.length();
    }

    /**
     * 文本监听器
     *
     * @param str 当前最新文本
     * @param start 旧文本起始位置
     * @param before 旧文本长度
     * @param count 文本长度的变化
     */

    @Override
    public void onTextUpdated(String str, int start, int before, int count) {
        if (cursorIndex == CONSTANT_2 && str.length() > CONSTANT_3) { // 在输入数字时,如果光标定位在第三位进行删除,就不给删除 防止出现 +7( 扩号被删
            setText(recordText);
            return;
        }
        textChange(str);
        if (!isEditingOnChanged && isEditingBefore) { // 此处为要显示的文本进行过滤操作
            isEditingOnChanged = true;
            if (isIgnore) {
                setText(recordText); // 当输入号码数大于限制个数时固定显示缓存文本
                return;
            }
            if (str.length() > recordTextLength) { // 增加文本
                textChange2(str, count);
            }
        }
        if (!isEditingAfter && isEditingBefore && isEditingOnChanged) { // 此处显示文本操作
            textChange3(str);
        }
        setBubbleSize(0, 0); // 输入完成隐藏光标下方的圆球
    }

    private void textChange3(String str) {
        isEditingAfter = true;
        if (hasHint() && (isKeepHint || rawText.length() == 0)) {
            if (str.length() < CONSTANT_3) {
                // 判断当前文本长度是否小于3 如果是就显示默认+7(   这种格式
                setText(recordText);
            } else { // 当rawText.length() == 0 会 进来判断是否输入其他特殊字符
                Pattern pattern = Pattern.compile(REGEX);
                Matcher matcher = pattern.matcher(str);
                if (!isIgnore && matcher.find() && str.length() < mask.length()) {
                    setText(mask.substring(0, CONSTANT_3)); // 判断是否为特殊字符,如果是就显示默认格式 +7(
                    isFirstCheck = true;
                }
            }
        } else {
            operationText(str);
        }
        isSelectionChanged = false;
        isEditingBefore = false;
        isEditingOnChanged = false;
        isEditingAfter = false;
        isIgnore = false;
    }

    private void operationText(String str) {
        if (isFirstCheck) {
            if (str.length() < recordTextLength) {
                // 判断是否显示hint文本，如果显示并且按删除按钮是清空隐藏文本
                isFirstCheck = false;
                String maskedText = makeMaskedText();
                String replace = maskedText.replace(CONSTANT_STR, "");
                setText(replace);
                recordText = replace; // 重新赋值
                recordTextLength = replace.length();
                rawText.setText(""); // 恢复默认为空
            } else {
                // 该操作是初始化显示的时候在任意位置添加数字和超过规定字数时显示hint文本
                // 如果有定位光标的方法就删除这个操作
                if (isIgnore) {
                    setText(recordText);
                } else { // 过滤其他特殊字符
                    isFirstCheck = false;
                    String maskedText = makeMaskedText();
                    String replace = maskedText.replace(CONSTANT_STR, "");
                    setText(replace);
                    recordText = replace; // 重新赋值
                    recordTextLength = replace.length();
                }
            }
        } else {
            if (rawText.getText().contains(CONSTANT_STR)) { // 判断是否有包含特殊符号
                rawText.setText(rawText.getText().replace(CONSTANT_STR, ""));
            }
            showMaskedText();
        }
    }

    private int textChange2(String str, int count) {
        int tempCount = count;
        int startingPosition = maskToRaw[nextValidPosition(recordIndex)];
        String addedString = str.subSequence(recordIndex, recordIndex + 1).toString();
        tempCount = rawText.addToString(clear(addedString), startingPosition, maxRawLength);
        if (isInitialized) {
            int currentPosition;
            if (startingPosition + tempCount < rawToMask.length) {
                currentPosition = rawToMask[startingPosition + tempCount];
            } else {
                currentPosition = lastValidMaskPosition + 1;
            }
            return currentPosition;
        }
        return tempCount;
    }

    private void textChange(String str) {
        if (!isEditingBefore) {
            isEditingBefore = true;
            checkCharIndex(str, recordText);
            if (str.length() > lastValidMaskPosition + 1) {
                isIgnore = true;
            }
            int rangeStart = recordIndex;
            Range range;
            if (str.length() < recordTextLength) { // 删除文本   如果当前文本小于旧文本长度 重新赋值新的索引
                rangeStart = erasingStart(recordIndex);
                if (str.length() < recordText.length() && recordText.length() == CONSTANT_3) {
                    // 判断当前输入框是否只剩下 +7( 这个格式, 如果是就设置特殊符号,为了不走showMaskedTextHint（）这个方法
                    range = calculateRange(rangeStart, recordIndex + (recordTextLength - str.length()));
                    rawText.setText(CONSTANT_STR);
                } else {
                    range = calculateRange2(rangeStart, recordIndex + (recordTextLength - str.length()));
                }
            } else { // 增加文本
                range = calculateRange(rangeStart, recordIndex);
            }
            if (range.getStart() != CONSTANT) {
                if (isFirstCheck) {
                    // 设置特殊符号让rawText的length不等于0 就不走showMaskedTextHint（）这个方法
                    // 如果有定位光标的方法就删除这个操作
                    rawText.setText(CONSTANT_STR);
                } else {
                    rawText.subtractFromString(range);
                }
            }
        }
    }

    /**
     * 退出到后台取消光标下的圆球
     */
    public void cancelCursor() {
        setBubbleSize(0, 0);
    }

    /**
     * 移除所有消息时间
     */
    public void removeAllComponent() {
        if (eventHandler != null) {
            eventHandler.removeAllEvent();
        }
    }
}

